/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package dale2507.gates.gate;

import dale2507.gates.GateDirectory;
import dale2507.gates.GatePlugin;
import dale2507.gates.Messenger;
import dale2507.gates.data.Settings;
import dale2507.gates.data.permissions.Permissions;
import dale2507.gates.events.ChevronDialledEvent;
import dale2507.gates.events.GateActivateEvent;
import dale2507.gates.events.GateDeactivateEvent;
import dale2507.gates.events.GateTravelEvent;
import dale2507.gates.exceptions.GateConnectionException;
import dale2507.gates.exceptions.PermissionException;
import dale2507.gates.gate.comparator.GateAddressComparator;
import dale2507.gates.gate.logic.IActivatorLogic;
import java.lang.reflect.InvocationTargetException;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.scheduler.BukkitScheduler;

/**
 *
 * @author Dale
 */
public class Activator {
    
    private static Class<? extends IActivatorLogic> logic = Settings.getInstance().connection.activatorLogicClass();
    
    public static void setActivatorLogic(Class<? extends IActivatorLogic> logic) {
        Activator.logic = logic;
    }
    
    private Gate source;
    
    private BukkitScheduler scheduler = Bukkit.getScheduler();
    
    private CommandSender cs = null;
    private Address address = null;
    private Gate destination = null;
    private IActivatorLogic l = null;
    
    private int deactivatorTaskId;
    private AutoOff autoOff;
    
    Activator(Gate source) {
        this.source = source;
    }

    public Gate getDestination() {
        return destination;
    }
    
    public void activate(CommandSender cs) throws PermissionException, GateConnectionException {
        this.activate(cs, null);
    }
    
    public void activate(CommandSender cs, Address address) throws PermissionException, GateConnectionException {
        this.cs = cs;
        this.address = address;
        
        // Get address if none supplied
        if (this.address == null || this.address.isEmpty()) {
            Address a = this.source.getDhd().getAddress();
            if (a == null || a.isEmpty()) {
                this.address = this.source.getSettings().getAutoDialAddress();
            } else {
                this.address = a;
            }
        }
        
        // Check permission
        if (cs instanceof Player) {
            if (!Permissions.getPermissions().getUsePermissions().allowGateActivate((Player) cs))
                throw new PermissionException("You do not have permission to activate gates");
        }
        
        // Source gate checks
        if (this.source.getStatus() == Gate.Status.DISABLED) throw new GateConnectionException("Source gate is disabled");
        if (this.source.getStatus() != Gate.Status.INACTIVE) throw new GateConnectionException("Source gate is busy");
        if (!this.source.getRing().isClear()) throw new GateConnectionException("Source gate is blocked");
        if (this.address == null || this.address.isEmpty()) throw new GateConnectionException("This gate does not support auto dial");
        
        // Start logic
        try {
            this.l = Activator.logic.getConstructor(Activator.class, CommandSender.class).newInstance(this, this.cs);
            this.l.run();
        } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException ex) {
            Logger.getLogger(Activator.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    
    public void setSourceStatus(Gate.Status status) {
        this.source.setStatus(status);
    }
    
    public void setDestinationStatus(Gate.Status status) {
        this.destination.setStatus(status);
    }
    
    public void findDestination() throws GateConnectionException {
        this.destination = GateDirectory.getInstance().getGate(new GateAddressComparator(), address);
        if (this.destination == null) throw new GateConnectionException("Address does not exist");
    }
    
    public void checkDestination() throws GateConnectionException, PermissionException {
        if (this.destination == source) throw new GateConnectionException("Cannot connect to same gate as source");
        if (this.destination.getStatus() == Gate.Status.DISABLED) throw new GateConnectionException("Destination gate is disabled");
        if (this.destination.getStatus() != Gate.Status.INACTIVE) throw new GateConnectionException("Destination gate is busy");
        if (!this.destination.getRing().isClear()) throw new GateConnectionException("Destination gate is blocked");
        
        // Check for private gate
        if (this.cs instanceof Player && this.destination.getSettings().getVisibility() == GateSettings.PRIVATE) {
            Player p = (Player) this.cs;
            if (!this.destination.getSettings().getOwner().equalsIgnoreCase(p.getName()) && 
                    !Permissions.getPermissions().getBypassPermissions().bypassPrivate(p))
                throw new PermissionException("The destination gate is private");
        }
    }
    
    public boolean dialChevron(int i) {
        ChevronDialledEvent cde = new ChevronDialledEvent(this.source, i);
        Bukkit.getPluginManager().callEvent(cde);
        if (cs != null) Messenger.sendMessage(cs, Messenger.SUCCESS, "Chevron " + (i+1) + " engaged");
        
        int total = this.address.getItemCount();
        if (i < (total) / 2) {
            this.source.getRing().setChevron(i, true);
            if (this.destination != null) this.destination.getRing().setChevron(i, true);
        } else {
            this.source.getRing().setChevron(i + (9 - total), true);
            if (this.destination != null) this.destination.getRing().setChevron(i + (9 - total), true);
            if (i == (this.address.getItemCount() - 1)) return true;
        }
        return false;
    }
    
    public void establish() {
        GateActivateEvent e = new GateActivateEvent(this.source, this.destination);
        Bukkit.getPluginManager().callEvent(e);
        
        this.source.setStatus(Gate.Status.OUTGOING);
        this.destination.setStatus(Gate.Status.INCOMING);

        if (cs != null) Messenger.sendMessage(cs, Messenger.SUCCESS, this.source.getSettings().getName() + " gate activated");
        if (!this.source.getSettings().isIndefinite() || Settings.getInstance().creation.indefiniteConnectionItem() == null) {
            // schdule shutdown
            deactivatorTaskId = scheduler.scheduleSyncDelayedTask(GatePlugin.getInstance(), new Runnable() {
                @Override
                public void run() {
                    try {
                        deactivate(cs);
                    } catch (PermissionException ex) {
                        Logger.getLogger(Activator.class.getName()).log(Level.SEVERE, null, ex);
                    }
                }
            }, Settings.getInstance().connection.maximumConnectionTime());
            autoOff = new AutoOff();
        }
    }

    public void chevronsOff() {
        throw new UnsupportedOperationException("Not yet implemented");
    }
    
    private class AutoOff implements Runnable, Listener {
        
        private int taskId;
        private long time = Settings.getInstance().connection.autoOffTime();
        
        AutoOff() {
            this.refresh();
            Bukkit.getPluginManager().registerEvents(this, GatePlugin.getInstance());
        }
        
        private void refresh() {
            if (taskId > 0) scheduler.cancelTask(taskId);
            taskId = scheduler.scheduleSyncDelayedTask(GatePlugin.getInstance(), this, time);
        }
        
        void destroy() {
            scheduler.cancelTask(taskId);
            HandlerList.unregisterAll(this);
            autoOff = null;
        }

        @Override
        public void run() {
            try {
                deactivate(cs);
            } catch (PermissionException ex) {
                Logger.getLogger(Activator.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        
        @EventHandler(priority = EventPriority.NORMAL)
        public void onTraveller(GateTravelEvent event) {
            if (event.getSource() == source) {
                this.refresh();
            }
        }
        
    }
    
    public void deactivate(CommandSender cs) throws PermissionException {
        if (this.source.getStatus() == Gate.Status.OUTGOING ||
                this.source.getStatus() == Gate.Status.DIALLING) {
            GateDeactivateEvent e = new GateDeactivateEvent(this.source, this.destination);
            Bukkit.getPluginManager().callEvent(e);

            // Check permission
            if (cs != null && cs instanceof Player) {
                if (!Permissions.getPermissions().getUsePermissions().allowGateDeactivate((Player) cs))
                    throw new PermissionException("You do not have permission to deactivate gates");
            }

            if (this.source.getStatus() == Gate.Status.DIALLING) l.stop();

            this.source.setStatus(Gate.Status.INACTIVE);
            if (this.destination != null) this.destination.setStatus(Gate.Status.INACTIVE);

            if (this.destination != null) this.destination = null;

            if (autoOff != null) autoOff.destroy();
            if (cs != null) Messenger.sendMessage(cs, Messenger.SUCCESS, this.source.getSettings().getName() + " gate deactivated");
            scheduler.cancelTask(deactivatorTaskId);
        } else if (this.source.getStatus() == Gate.Status.INCOMING ||
                this.source.getStatus() == Gate.Status.RECEIVING) {
            if (cs != null) Messenger.sendMessage(cs, Messenger.ERROR, "Gate connection cannot be deactivated. Connection is incoming.");
        } else {
            if (cs != null) Messenger.sendMessage(cs, Messenger.ERROR, "Gate connection cannot be deactivated. There is no connection active.");
        }
    }
    
}
